package com.rw.engine.sign.aspect;

import com.rw.engine.sign.anno.SignParam;
import com.rw.engine.sign.exception.SignException;
import com.rw.engine.sign.util.FlatMap;
import com.rw.tool.util.string.StringUtil;
import org.springframework.util.ClassUtils;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * 默认的签名校验器
 * 如果校验签名不成功，则抛出异常
 */
public class DefaultSignRule implements SignRule {
    @Override
    public void check(Map<SignParam, Object> signParam) {
        // 获取所有参数的字典序排序
        TreeMap<String, String> treeMap = getTreeMap(signParam);
        // 判断所有参数的字典序排序是否符合签名要求
        assertTreeMap(treeMap);
    }

    /**
     * 根据注解情况，获取所有参数的字典序排序
     *
     * @param signParam 注解与参数
     * @return 字典序排序
     */
    protected TreeMap<String, String> getTreeMap(Map<SignParam, Object> signParam) {
        TreeMap<String, String> result = new TreeMap<>();
        // 遍历参数
        for (Map.Entry<SignParam, Object> entry : signParam.entrySet()) {
            String name = entry.getKey().value();
            Object obj = entry.getValue();

            // 判断这个参数是否是基本类型
            if (Objects.isNull(obj)) {
                result.put(name, StringUtil.EMPTY);
            } else if (ClassUtils.isPrimitiveWrapper(obj.getClass()) || obj.getClass() == String.class) {
                if (StringUtil.isBlank(name)) {
                    throw new IllegalArgumentException("Name of the primitive signature cannot be empty!");
                }
                result.put(name, obj.toString());
            } else {
                result.putAll(FlatMap.flatMapObj(obj));
            }
        }

        return result;
    }

    /**
     * 根据所有参数的字典序排序，判断Sign是否符合预期
     * 默认情况下，是把所有参数使用 a=1&b=2 拼接起来变成字符串
     * 然后进行MD5变换，和SIGN做比对
     *
     * @param treeMap 所有参数的字典序排序
     */
    protected void assertTreeMap(TreeMap<String, String> treeMap) {
        String sign = treeMap.remove("sign");

        String paramStr = treeMap.entrySet().stream()
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .collect(Collectors.joining("&"));

        String md5 = getMD5(paramStr);

        if (!StringUtil.equalsIgnoreCase(sign, md5)) {
            throw new SignException("Signature verification failed!", treeMap);
        }
    }

    /**
     * 获取MD5加密
     */
    protected String getMD5(String param) {
        return DigestUtils.md5DigestAsHex(param.getBytes(StandardCharsets.UTF_8));
    }
}
